<?php

namespace yunj\builder;

use yunj\Config;
use yunj\Validate;
use yunj\enum\BuilderType;

final class YunjImport extends Builder {

    protected $type = BuilderType::IMPORT;

    protected $builderValidateClass = "\\yunj\\validate\\YunjImport";

    protected $scriptFileList = ["/static/yunj/js/import.min.js?v=" . YUNJ_VERSION];

    protected $options = ["sheets", "cols", "colsValidate", "limit", "tips", "row", "rows", "validate"];

    /**
     * 必填：工作表
     * "sheets"=>["Sheet1","Sheet2"]
     * @var array
     */
    private $sheets;

    /**
     * 必填：工作表表头
     * "cols"=>[
     *      "Sheet1"=>[],
     *      "Sheet2"=>[
     *          "name"=>[
     *              "title"=>"姓名",
     *              "default"=>"小王",
     *              "verify"=>"require|chs",
     *              "desc"=>"必填，只能输入汉字",
     *          ],...
     *      ],...
     * ]
     * @var array
     */
    private $cols;

    // 工作表表头验证的回调方法
    private $colsValidateCallback;

    /**
     * 每次导入条数，默认20条
     * "limit"=>20
     * @var int
     */
    private $limit = 20;

    /**
     * 提示语
     * "tips"=>[
     *      "提示一",
     *      "提示二",
     * ]
     * @var array
     */
    private $tips = [];

    // 单行数据处理回调
    private $rowCallback;

    // 多行数据处理回调
    private $rowsCallback;

    protected function setAttr(array $args = []): void {
        parent::setAttr($args);
        $this->config = Config::get('import', []);
    }

    /**
     * Notes: 工作表
     * Author: Uncle-L
     * Date: 2021/4/10
     * Time: 17:21
     * @param callable|array $sheets
     * callable:
     *      function(){
     *          return ["Sheet1","Sheet2"];
     *      }
     * array:
     *      ["Sheet1","Sheet2"]
     * @return YunjImport
     */
    public function sheets($sheets) {
        if ($this->existError()) return $this;
        if (!is_callable($sheets)) {
            $callable = function () use ($sheets) {
                return $sheets;
            };
        } else {
            $callable = $sheets;
        }
        $this->setOptionCallbale('sheets', $callable);
        return $this;
    }

    /**
     * Notes: 执行工作表回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjImport
     */
    protected function exec_sheets_callable(callable $callable) {
        if ($this->existError()) return $this;
        $sheets = $callable();
        if (!is_array($sheets)) return $this->error("YunjForm [sheets] 需返回有效数组");
        $this->sheets = $sheets;
        return $this;
    }

    /**
     * Notes: 工作表表头
     * Author: Uncle-L
     * Date: 2021/4/10
     * Time: 17:23
     * @param callable $callable
     * @return YunjImport
     */
    public function cols(callable $callable) {
        if ($this->existError()) return $this;
        $this->setOptionCallbale('cols', $callable);
        return $this;
    }

    /**
     * Notes: 执行工作表表头回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjImport
     */
    protected function exec_cols_callable(callable $callable) {
        if ($this->existError()) return $this;
        $cols = $this->cols ? $this->cols : [];
        foreach ($this->sheets as $sheet) {
            $sheetCols = $callable($sheet);
            if (!is_array($sheetCols)) return $this->error("YunjImport [cols] 需返回有效数组");
            $cols[$sheet] = isset($cols[$sheet]) ? $cols[$sheet] : [];
            foreach ($sheetCols as $key => $field) {
                $cols[$sheet][$key] = $field + ["title" => "", "default" => "", "verify" => "", "desc" => ""];
            }
        }
        $this->cols = $cols;
        return $this;
    }

    /**
     * Notes: 工作表表头的验证器
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:05
     * @param callable $callable
     * callable:
     *      function(){
     *          return new TestValidate();
     *      }
     * @return YunjImport
     */
    public function colsValidate(callable $callable) {
        if ($this->existError()) return $this;
        $this->colsValidateCallback = $callable;
        return $this;
    }

    /**
     * Notes: 每次导入条数
     * Author: Uncle-L
     * Date: 2021/4/13
     * Time: 19:40
     * @param callable|int $limit
     * callable:
     *      function(){
     *          return 20;
     *      }
     * array:
     *      20
     * @return YunjImport
     */
    public function limit($limit) {
        if ($this->existError()) return $this;
        if (!is_callable($limit)) {
            $callable = function () use ($limit) {
                return $limit;
            };
        } else {
            $callable = $limit;
        }
        $this->setOptionCallbale('limit', $callable);
        return $this;
    }

    /**
     * Notes: 执行每次导入条数回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjImport
     */
    protected function exec_limit_callable(callable $callable) {
        if ($this->existError()) return $this;
        $limit = $callable();
        if (!is_positive_integer($limit)) return $this->error("YunjImport [limit] 需返回正整数");
        $this->limit = $limit;
        return $this;
    }

    /**
     * Notes: 提示语
     * Author: Uncle-L
     * Date: 2021/4/10
     * Time: 17:22
     * @param callable|array $tips
     * callable:
     *      function(){
     *          return ["提示一","提示二"];
     *      }
     * array:
     *      ["提示一","提示二"]
     * @return YunjImport
     */
    public function tips($tips) {
        if ($this->existError()) return $this;
        if (!is_callable($tips)) {
            $callable = function () use ($tips) {
                return $tips;
            };
        } else {
            $callable = $tips;
        }
        $this->setOptionCallbale('tips', $callable);
        return $this;
    }

    /**
     * Notes: 执行提示语回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjImport
     */
    protected function exec_tips_callable(callable $callable) {
        if ($this->existError()) return $this;
        $tips = $callable();
        if (!is_array($tips) || !array_depth($tips, 1)) return $this->error("YunjImport [tips] 需返回一维数组");
        $this->tips = $tips;
        return $this;
    }

    /**
     * Notes: 单行数据处理
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:04
     * @param callable $callable
     * @return YunjImport
     */
    public function row(callable $callable) {
        if ($this->existError()) return $this;
        $this->rowCallback = $callable;
        return $this;
    }

    /**
     * Notes: 多行数据处理
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:04
     * @param callable $callable
     * @return YunjImport
     */
    public function rows(callable $callable) {
        if ($this->existError()) return $this;
        $this->rowsCallback = $callable;
        return $this;
    }

    protected function viewArgs() {
        $args = parent::viewArgs();
        $args['sheets'] = $this->sheets;
        $args['cols'] = $this->cols;
        $args['limit'] = $this->limit;
        $args['tips'] = array_merge($this->config["tips"], $this->tips);
        return $args;
    }

    /**
     * Notes: 异步数据导入
     * Author: Uncle-L
     * Date: 2021/10/12
     * Time: 16:34
     * @param array $data
     * @return array
     */
    protected function async_import(array $data) {
        $this->execOptionsCallbale("sheets,cols,colsValidate,limit,row,rows");
        if ($this->existError()) throw_error_json($this->getError());
        if (!$this->rowCallback && !$this->rowsCallback) throw_error_json("YunjImport [row]或[rows] 未设置");
        if ($data["count"] > $this->limit) throw_error_json("请求数量超过限制{$this->limit}");
        $validate = $this->colsValidateCallback ? call_user_func($this->colsValidateCallback) : (new Validate());
        if (!($validate instanceof Validate)) throw_error_json("YunjImport [colsValidate] 需返回 \\yunj\\Validate 实例对象");

        $rows = $data['rows'];
        $passRows = [];
        $passIdKeyMap = [];
        foreach ($rows as $rk => $rv) {
            $sheet = $rv['sheet'];
            $id = $rv['id'];
            $row = $rv['row'];
            $res = $this->async_import_validate($validate, $sheet, $row, "row");
            if ($res !== true) {
                $rows[$rk]['response_error'] = $res;
                continue;
            }
            if ($this->rowCallback) {
                $res = call_user_func_array($this->rowCallback, [$sheet, $row]);
                if ($res !== true) $rows[$rk]['response_error'] = $res;
                continue;
            }
            $rv["row"] = $row;
            $passRows[$id] = $rv;
            $passIdKeyMap[$id] = $rk;
        }
        if ($passRows && $this->rowsCallback) {
            $error = call_user_func_array($this->rowsCallback, [$passRows]);
            if ($error && is_array($error))
                foreach ($error as $id => $msg)
                    if (array_key_exists($id, $passIdKeyMap)) $rows[$passIdKeyMap[$id]]["response_error"] = $msg;
        }
        $response = [
            'limit' => $this->limit,
            'count' => $data['count'],
            'rows' => $rows
        ];
        return $response;
    }

    /**
     * Notes: 异步处理导入数据提交验证
     * Author: Uncle-L
     * Date: 2021/10/21
     * Time: 14:44
     * @param Validate $validate
     * @param string $sheet
     * @param array $row
     * @param string $scene
     * @return bool|string  [通过返回true，失败返回失败原因]
     */
    private function async_import_validate(Validate $validate, string $sheet, array &$row, string $scene) {
        if (!in_array($sheet, $this->sheets)) return "工具表{$sheet}错误";
        if (!isset($this->cols[$sheet])) return "工具表{$sheet}表头不存在";
        static $sheetRuleAndFieldMap;
        $sheetRuleAndFieldMap = $sheetRuleAndFieldMap ?: [];
        if (!isset($sheetRuleAndFieldMap[$sheet])) {
            $cols = $this->cols[$sheet];
            $rule = [];
            $field = [];
            foreach ($cols as $ck => $cv) {
                if (!isset($cv['verify']) || !$cv['verify']) continue;
                $rule[$ck] = $cv["verify"];
                $field[$ck] = $cv['title'] ?: $ck;
            }
            $sheetRuleAndFieldMap[$sheet] = [$rule, $field];
        }
        list($rule, $field) = $sheetRuleAndFieldMap[$sheet];
        if (!$rule) return true;
        $row["sheet"] = $sheet;
        $res = $this->dataValidate($validate, $rule, $field, $row, $scene);
        if ($res!==true) return $validate->getError();
        return true;
    }


}